home *** CD-ROM | disk | FTP | other *** search
- #! /usr/bin/perl
-
- use strict;
- use warnings;
-
- #man{{{
-
- =head1 NAME
-
- tails-security-check
-
- =head1 VERSION
-
- Version X.XX
-
- =cut
-
-
- =head1 DESCRIPTION
-
- =head1 SYNOPSIS
-
- tails-security-check [ ATOM_FEED_BASE_URL [ BUILD_DATE ] ]
-
- ATOM_FEED_BASE_URL will be appended /index.XX.atom,
- for XX in (current locale's language code, 'en'),
- until success is reported by the HTTP layer.
-
- =head1 AUTHOR
-
- Tails dev team <amnesia@boum.org>
- See https://tails.boum.org/.
-
- =cut
-
- #}}}
-
- use feature qw/say/;
- use Carp;
- use DateTime;
- use DateTime::Format::ISO8601;
- use Desktop::Notify;
- use Fatal qw( open close );
- use Locale::gettext;
- use POSIX;
- use XML::Atom;
- use XML::Atom::Feed;
-
- ### Initialization
-
- use IO::Socket::SSL;
- use Net::SSLeay;
- BEGIN {
- IO::Socket::SSL::set_ctx_defaults(
- verify_mode => Net::SSLeay->VERIFY_PEER(),
- ca_file => '/etc/ssl/certs/UTN_USERFirst_Hardware_Root_CA.pem',
- );
- }
- use LWP::UserAgent; # needs to be *after* IO::Socket::SSL's initialization
-
- setlocale(LC_MESSAGES, "");
- textdomain("tails-security-check");
-
- ### configuration
-
- my $version_file = '/etc/amnesia/version';
- my $default_base_url = 'https://tails.boum.org/security/';
-
- ### helper subs
-
- =head2 build_date
-
- Argument: file which the version information must be extracted from.
-
- Returns a DateTime object that represents the build time extracted
- from the version file.
-
- =cut
- sub build_date {
- my $version_file = shift;
-
- open my $version_h, '<', $version_file;
- my ($version, $date) = ( <$version_h> =~ /(.*) - (\d+)(?:\n)?$/ );
- close $version_h;
- if (!defined $date || !$date) {
- croak gettext("Unparseable line in %s", $version_file);
- }
- return DateTime::Format::ISO8601->parse_datetime( $date );
- }
-
- =head2 current_lang
-
- Returns the two-letters language code of the current session.
-
- =cut
- sub current_lang {
- my ($code) = ($ENV{LANG} =~ m/([a-z]{2}).*/);
-
- return $code;
- }
-
- =head2 atom_str
-
- Argument: an Atom feed URL
-
- Returns the Atom's feed content on success, undef on failure.
-
- =cut
- sub atom_str {
- my $url = shift;
-
- if (!defined $url) {
- croak gettext("atom_str was passed an undefined argument");
- }
-
- $ENV{HTTPS_VERSION} = 3;
-
- my $ua = LWP::UserAgent->new;
- $ua->proxy([qw(http https)] => 'socks://127.0.0.1:9062');
- my $req = HTTP::Request->new('GET', $url);
- my $res = $ua->request($req);
- if (defined $res && $res->is_success) {
- return $res->content;
- }
-
- return undef;
- }
-
- =head2 is_newer_than
-
- Arguments: a XML::Atom::Entry, a DateTime object
-
- Returns true if, and only if, the published field of the Atom entry is
- newer than the time represented by the DateTime object.
-
- =cut
- sub is_newer_than {
- my $entry = shift;
- my $ref_dt = shift;
-
- my $entry_published_dt = DateTime::Format::ISO8601->parse_datetime($entry->published);
- if (DateTime->compare( $entry_published_dt, $ref_dt) == 1) {
- return 1;
- }
- return undef;
- }
-
- =head2 get_new_entries
-
- Arguments: the Atom feed URL, a DateTime reference object.
-
- Returns the list of XML::Atom::Entry's, taken from the feed, that have
- been published after the reference time.
-
- We use this poor man's manual Accept-Language algorithm as the website
- layout does not allow us to use content negotiation.
-
- =cut
- sub get_new_entries {
- my $base_url = shift;
- my $since_dt = shift;
-
- my $separator = '';
- $separator = '/' unless $base_url =~ m{/\z}xms;
-
- my @try_urls = (
- $base_url . $separator . 'index.' . current_lang() . '.atom',
- $base_url . $separator . 'index.en.atom',
- );
-
- my $feed_str;
- foreach my $url (@try_urls) {
- last if ($feed_str = atom_str($url));
- }
-
- if (!defined $feed_str) {
- croak gettext("Empty fetched feed.");
- }
- my $feed = XML::Atom::Feed->new(\$feed_str);
- return grep { is_newer_than($_, $since_dt) } $feed->entries();
- }
-
- =head2 notify_user
-
- Use the Desktop Notifications framework to notify the user about the
- Atom entries passed as arguments.
-
- =cut
- sub notify_user {
- my @entries = @_;
-
- my $notify = Desktop::Notify->new();
-
- my $summary = gettext('This version of Tails has known security issues:');
- my $body = '';
-
- map { $body = $body
- . '- '
- . '<a href="'
- . $_->id
- . '">'
- . $_->title
- . '</a>'
- . "\n";
- } @entries;
-
- $notify->create(summary => $summary,
- body => $body,
- timeout => 0)->show();
- }
-
- ### sanity checks
-
- if (! -e "$version_file") {
- die "The Tails version file ($version_file) does not exist."
- }
- if (! -r "$version_file") {
- die "The Tails version file ($version_file) is not readable."
- }
-
- ### parse command line args
-
- my $base_url = shift || $default_base_url;
- my $opt_since = shift;
- my $since_dt;
- if (defined $opt_since) {
- $since_dt = DateTime::Format::ISO8601->parse_datetime($opt_since);
- }
- else {
- $since_dt = build_date($version_file);
- }
-
- ### main
-
- my @newer_entries = get_new_entries($base_url, $since_dt);
-
- if (! @newer_entries) {
- exit 0;
- }
- else {
- notify_user(@newer_entries);
- }
-